﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using JsonRpcRT.BasicCommunication;
using JsonRpcRT.RemoteMethodsHandling;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace JsonRpcRT
{
    public delegate Task<MethodResponse> RemoteMethodExecutionEventHandler(Object methodParams);

    public delegate void RemoteNotificationExecutionEventHandler(Object methodParams);

    internal struct VoidTypeStruct
    {
    }

    public class JsonRpcController
    {
        private const string ProtocolVersion = "2.0";

        public readonly BasicSocketOperationsController _basicSocketOperationsController;
        private readonly AsyncLock _recieveLock = new AsyncLock();

        private readonly Dictionary<string, RemoteMethodExecutionEventHandler> _remoteMethodExecutors;
        private readonly Dictionary<string, RemoteNotificationExecutionEventHandler> _remoteNotificationExecutors;

        private readonly ConcurrentDictionary<int, Tuple<TaskCompletionSource<VoidTypeStruct>, MethodResponse>>
            _requestsDictionary;

        public int MaximumBufferLength = 64000;
        public int RecieveBufferLength = 1024;
        public int RequestTimeout = 5000;

        private string _recievedDataBuffer = "";

        private int _uniqueIdHolder;

        public JsonRpcController(string hostName, string hostPort, bool showSocketLevelLogs = false)
        {
            _uniqueIdHolder = 0;
            _requestsDictionary =
                new ConcurrentDictionary<int, Tuple<TaskCompletionSource<VoidTypeStruct>, MethodResponse>>();

            _remoteMethodExecutors = new Dictionary<string, RemoteMethodExecutionEventHandler>();
            _remoteNotificationExecutors = new Dictionary<string, RemoteNotificationExecutionEventHandler>();

            _basicSocketOperationsController = new BasicSocketOperationsController(hostName, hostPort,
                                                                                   (uint) RecieveBufferLength);
            if (showSocketLevelLogs) _basicSocketOperationsController.Log += BasicSocketOperationsControllerLog;
            _basicSocketOperationsController.DataRecieved += BasicSocketOperationsControllerDataRecieved;
        }

        private int UniqueId
        {
            get
            {
                _uniqueIdHolder++;
                return _uniqueIdHolder;
            }
        }

        public bool IsConnected
        {
            get { return _basicSocketOperationsController.IsConnected; }
        }

        public event LogEventHandler NewLog;

        private void OnLog(string text)
        {
            LogEventHandler handler = NewLog;
            if (handler != null) handler(text);
        }

        internal void Log(string text)
        {
            OnLog(text);
        }

        public async Task<bool> InitAsync()
        {
            bool returnValue = false;
            await Task.Run(async () => { returnValue = await Init(); });
            return returnValue;
        }

        private async Task<bool> Init()
        {
            bool initSuccessful = false;

            try
            {
                initSuccessful = await _basicSocketOperationsController.Init();
            }
            catch (Exception exception)
            {
                Log(String.Format("Error with JsonRpcController init. {0}", exception.Message));
            }

            return initSuccessful;
        }

        public void RegisterRemoteMethodExecutor(string methodName, RemoteMethodExecutionEventHandler handler)
        {
            _remoteMethodExecutors[methodName] = handler;
        }

        public void RegisterRemoteNotificationExecutor(string methodName,
                                                       RemoteNotificationExecutionEventHandler handler)
        {
            _remoteNotificationExecutors[methodName] = handler;
        }

        private async Task<MethodResponse> ExecuteRemoteMethod(MethodRequest request)
        {
            if (_remoteMethodExecutors.ContainsKey(request.MethodName))
            {
                MethodResponse response = await _remoteMethodExecutors[request.MethodName](request.MethodParams);
                response.Id = request.Id;
                response.MethodName = request.MethodName;

                return response;
            }

            return null;
        }

        private void ExecuteRemoteNotification(string methodName, Object methodParams)
        {
            if (_remoteNotificationExecutors.ContainsKey(methodName))
            {
                _remoteNotificationExecutors[methodName](methodParams);
            }
        }

        private async void BasicSocketOperationsControllerDataRecieved(string data)
        {
            using (await _recieveLock.LockAsync())
            {
                IEnumerable<string> messages = GetMessagesFromBuffer(data);

                foreach (string message in messages)
                {
                    ProcessMessage(message);
                }
            }
        }

        private IEnumerable<string> GetMessagesFromBuffer(string newData)
        {
            _recievedDataBuffer += newData;
            var parsedMessages = new Collection<string>();

            int numberOfOpenedCurlyBracketsInBuffer = 0;
            int numberOfOpenedSquareBracketsInBuffer = 0;
            int i = 0;
            while (i < _recievedDataBuffer.Length)
            {
                char currentChar = _recievedDataBuffer[i];

                if (currentChar == '\\')
                {
                    // skip character
                    i += 2;
                    continue;
                }

                if (currentChar == '{') numberOfOpenedCurlyBracketsInBuffer++;
                if (currentChar == '[') numberOfOpenedSquareBracketsInBuffer++;

                if (currentChar == '}') numberOfOpenedCurlyBracketsInBuffer--;
                if (currentChar == ']') numberOfOpenedSquareBracketsInBuffer--;

                if (numberOfOpenedCurlyBracketsInBuffer == 0 && numberOfOpenedSquareBracketsInBuffer == 0)
                {
                    string singleMessage = _recievedDataBuffer.Substring(0, i + 1);

                    if (singleMessage.Length > 1)
                    {
                        parsedMessages.Add(singleMessage);
                    }
                    else
                    {
                        Log(String.Format("Wrong data, skipping {0} character", singleMessage));
                    }
                    _recievedDataBuffer = _recievedDataBuffer.Substring(i + 1);

                    numberOfOpenedCurlyBracketsInBuffer = 0;
                    numberOfOpenedSquareBracketsInBuffer = 0;
                    i = 0;

                    continue;
                }

                i++;
            }

            if (_recievedDataBuffer.Length > MaximumBufferLength)
            {
                _recievedDataBuffer = string.Empty;
            }

            return parsedMessages;
        }

        private void ProcessMessage(string data)
        {
            try
            {
                object deserializedData = JsonConvert.DeserializeObject(data);

                if (deserializedData is JArray)
                {
                    var batchRequests = new Collection<MethodRequest>();

                    foreach (JToken message in (JArray) deserializedData)
                    {
                        MethodRequest request = ProcessSingleJsonMessage((JObject) message);

                        if (request != null)
                        {
                            batchRequests.Add(request);
                        }
                    }

                    if (batchRequests.Count > 0)
                    {
                        Task.Run(async () =>
                                           {
                                               var batchResponses = new Collection<MethodResponse>();

                                               foreach (MethodRequest batchRequest in batchRequests)
                                               {
                                                   MethodResponse response = await ExecuteRemoteMethod(batchRequest);

                                                   if (response != null)
                                                   {
                                                       batchResponses.Add(response);
                                                   }
                                               }

                                               if (batchResponses.Count > 0)
                                               {
                                                   SendBatchMethodResponses(batchResponses);
                                               }
                                           });
                    }
                }
                else if (deserializedData is JObject)
                {
                    MethodRequest request = ProcessSingleJsonMessage((JObject) deserializedData);

                    if (request != null)
                    {
                        Task.Run(async () => SendMethodResponse(await ExecuteRemoteMethod(request)));
                    }
                }
            }
            catch (Exception ex)
            {
                Log(String.Format("Error when recieving data {0}", ex.Message));
            }
        }

        private MethodRequest ProcessSingleJsonMessage(JObject recievedValue)
        {
            int id = -1;

            IList<string> keys = recievedValue.Properties().Select(p => p.Name).ToList();

            if (!keys.Contains("jsonrpc") || (string) recievedValue["jsonrpc"] != ProtocolVersion)
            {
                Log("Recieved correct JSON, but without protocol JSON-RPC version");
                return null;
            }

            if (!keys.Contains("id"))
            {
                // notification

                if (!keys.Contains("method"))
                {
                    Log("Probably recieved notification, but without method name");
                    return null;
                }

                var methodName = (string) recievedValue["method"];
                object menthodParams = keys.Contains("params") ? recievedValue["params"] : null;

                Task.Run(() => ExecuteRemoteNotification(methodName, menthodParams));

                return null;
            }

            if (keys.Contains("method"))
            {
                // remote method request

                if (!int.TryParse(recievedValue["id"].ToString(), out id))
                {
                    Log("Wrong method id");
                    return null;
                }

                var methodName = (string) recievedValue["method"];
                object methodParams = keys.Contains("params") ? recievedValue["params"] : null;

                return new MethodRequest {Id = id, MethodName = methodName, MethodParams = methodParams};
            }

            if ((keys.Contains("result") || keys.Contains("error")) &&
                (recievedValue["id"] == null || int.TryParse(recievedValue["id"].ToString(), out id)))
            {
                if (keys.Contains("result") && keys.Contains("error"))
                {
                    Log("Recieved resposne with method and error at the same - syntax error!");
                    return null;
                }

                // response for method
                if (_requestsDictionary.ContainsKey(id))
                {
                    TaskCompletionSource<VoidTypeStruct> tcs = _requestsDictionary[id].Item1;
                    var response = new MethodResponse();

                    if (keys.Contains("result"))
                    {
                        // normal response
                        response.Type = ResponseType.NormalResponse;
                        response.Content = recievedValue["result"];
                    }
                    else
                    {
                        // error response
                        response.Type = ResponseType.ErrorResponse;
                        response.Content = recievedValue["error"];
                    }

                    _requestsDictionary[id] = new Tuple<TaskCompletionSource<VoidTypeStruct>, MethodResponse>(tcs,
                                                                                                              response);
                    tcs.SetResult(new VoidTypeStruct());

                    return null;
                }

                if (recievedValue["id"] == null)
                {
                    Log("Recieved server error");
                    return null;
                }

                Log("Recieved resposne with invalid id");
                return null;
            }

            Log("Recieved unrecognized JSON");

            return null;
        }

        private void BasicSocketOperationsControllerLog(string text)
        {
            Log((text));
        }

        private static Dictionary<string, Object> CreateRequestDict(int? requestId, string methodName,
                                                                    Dictionary<string, object> methodParams)
        {
            var requestDict = new Dictionary<string, Object>();
            if (requestId != null)
            {
                requestDict["id"] = requestId;
            }
            requestDict["jsonrpc"] = "2.0";
            requestDict["method"] = methodName;

            if (methodParams.Count > 0)
            {
                requestDict["params"] = methodParams;
            }

            return requestDict;
        }

        private string PrepareRequestString(int? requestId, string methodName, Dictionary<string, object> methodParams)
        {
            try
            {
                return JsonConvert.SerializeObject(CreateRequestDict(requestId, methodName, methodParams));
            }
            catch (Exception ex)
            {
                Log(String.Format("Error when preparing request string {0}", ex.Message));

                return string.Empty;
            }
        }

        private static Dictionary<string, Object> CreateResponseDict(MethodResponse response)
        {
            var requestDict = new Dictionary<string, Object>();
            requestDict["id"] = response.Id;
            requestDict["jsonrpc"] = "2.0";
            requestDict["method"] = response.MethodName;
            requestDict[response.Type == ResponseType.NormalResponse ? "result" : "error"] = response.Content;

            return requestDict;
        }

        private string PrepareMethodResponse(MethodResponse response)
        {
            try
            {
                return JsonConvert.SerializeObject(CreateResponseDict(response));
            }
            catch (Exception ex)
            {
                Log(String.Format("Error when preparing response string {0}", ex.Message));

                return string.Empty;
            }
        }

        public async Task<bool> SendNotificationAsync(string methodName, Dictionary<string, object> methodParams)
        {
            bool sendSuccessfull = false;
            await Task.Run(async () => { sendSuccessfull = await SendNotification(methodName, methodParams); });
            return sendSuccessfull;
        }

        public async Task<bool> SendNotification(string methodName, Dictionary<string, object> methodParams)
        {
            string requestString = PrepareRequestString(null, methodName, methodParams);

            if (_basicSocketOperationsController != null || await Init())
            {
                return await _basicSocketOperationsController.Send(requestString);
            }

            return false;
        }

        public async Task<MethodResponse> SendRequestAsync(string methodName, Dictionary<string, object> methodParams)
        {
            MethodResponse returnValue = null;
            await
                Task.Run(async () => { returnValue = await SendRequest(methodName, methodParams); });
            return returnValue;
        }

        private async void SendBatchMethodResponses(IEnumerable<MethodResponse> responses)
        {
            var serializedBatchResponses = new Collection<object>();

            foreach (MethodResponse methodResponse in responses)
            {
                serializedBatchResponses.Add(CreateResponseDict(methodResponse));
            }

            if (_basicSocketOperationsController != null || await Init())
            {
                await _basicSocketOperationsController.Send(JsonConvert.SerializeObject(serializedBatchResponses));
            }
        }

        private async void SendMethodResponse(MethodResponse response)
        {
            string responseString = PrepareMethodResponse(response);

            if (_basicSocketOperationsController != null || await Init())
            {
                await _basicSocketOperationsController.Send(responseString);
            }
        }

        private async Task<MethodResponse> SendRequest(string methodName, Dictionary<string, object> methodParams)
        {
            if (_basicSocketOperationsController != null || await Init())
            {
                int requestId = UniqueId;
                string requestString = PrepareRequestString(requestId, methodName, methodParams);

                var tcs = new TaskCompletionSource<VoidTypeStruct>();

                _requestsDictionary[requestId] = new Tuple<TaskCompletionSource<VoidTypeStruct>, MethodResponse>(tcs,
                                                                                                                 null);


                if (await _basicSocketOperationsController.Send(requestString))
                {
                    try
                    {
                        await Task.Run(async () => { await tcs.Task; }).TimeoutAfter(RequestTimeout);
                    }
                    catch (TimeoutException)
                    {
                        Log(String.Format("Timeout for request it id = {0}", requestId));
                    }

                    if (_requestsDictionary[requestId].Item2 != null)
                    {
                        MethodResponse returnValue = _requestsDictionary[requestId].Item2;
                        ((IDictionary) _requestsDictionary).Remove(requestId);

                        return returnValue;
                    }

                    ((IDictionary) _requestsDictionary).Remove(requestId);
                    Log(string.Format("Request {0} timeout!", requestId));
                }
            }

            return null;
        }

        public async Task<bool> SendBatchRequestAsync(IEnumerable<BaseRemoteHandler> handlers)
        {
            bool returnValue = false;
            await
                Task.Run(async () => { returnValue = await SendBatchRequest(handlers); });
            return returnValue;
        }

        private async Task<bool> SendBatchRequest(IEnumerable<BaseRemoteHandler> handlers)
        {
            bool sendSuccessfull = true;

            if (_basicSocketOperationsController != null || await Init())
            {
                var requestsDict = new Collection<object>();
                var methodRequestIdsAndLocks =
                    new Collection<Tuple<int, TaskCompletionSource<VoidTypeStruct>, RemoteMethodRequestHandler>>();

                foreach (BaseRemoteHandler remoteHandler in handlers)
                {
                    int requestId = UniqueId;
                    requestsDict.Add(CreateRequestDict(requestId, remoteHandler.GetMethodName(),
                                                       remoteHandler.MethodParams));

                    if (remoteHandler is RemoteMethodRequestHandler)
                    {
                        var tcs = new TaskCompletionSource<VoidTypeStruct>();
                        methodRequestIdsAndLocks.Add(
                            new Tuple<int, TaskCompletionSource<VoidTypeStruct>, RemoteMethodRequestHandler>(requestId,
                                                                                                             tcs,
                                                                                                             remoteHandler
                                                                                                             as
                                                                                                             RemoteMethodRequestHandler));

                        _requestsDictionary[requestId] =
                            new Tuple<TaskCompletionSource<VoidTypeStruct>, MethodResponse>(tcs,
                                                                                            null);
                    }
                }

                if (await _basicSocketOperationsController.Send(JsonConvert.SerializeObject(requestsDict)))
                {
                    foreach (var tuple in methodRequestIdsAndLocks)
                    {
                        try
                        {
                            TaskCompletionSource<VoidTypeStruct> task1 = tuple.Item2;
                            await Task.Run(async () => { await task1.Task; }).TimeoutAfter(RequestTimeout);
                        }
                        catch (TimeoutException)
                        {
                            Log(String.Format("Timeout for request it id = {0}", tuple.Item1));
                            sendSuccessfull = false;
                            break;
                        }
                    }

                    foreach (var tuple in methodRequestIdsAndLocks)
                    {
                        if (_requestsDictionary[tuple.Item1].Item2 != null)
                        {
                            tuple.Item3.ParseResponse(_requestsDictionary[tuple.Item1].Item2);
                        }
                        else
                        {
                            sendSuccessfull = false;
                        }

                        ((IDictionary) _requestsDictionary).Remove(tuple.Item1);
                    }
                }
            }
            else
            {
                sendSuccessfull = false;
            }

            return sendSuccessfull;
        }
    }
}